(module interp (lib "eopl.ss" "eopl")
  
  (require "drscheme-init.scm")

  (require "lang.scm")
  (require "data-structures.scm")
  (require "environments.scm")

  (provide value-of-program value-of)

;;;;;;;;;;;;;;;; the interpreter ;;;;;;;;;;;;;;;;

  ;; value-of-program : program -> expval

  (define value-of-program 
    (lambda (pgm)
      (cases program pgm
        (a-program (body)
          (value-of body (init-env))))))

  ;; value-of : expression * environment -> expval

  (define value-of
    (lambda (exp env)
      (cases expression exp

        (lit-exp (num) (num-val num))

        (var-exp (id) (apply-env env id))

        (diff-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (- val1 val2))))

        (plus-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (+ val1 val2))))

        (times-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (* val1 val2))))

        (div-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (quotient val1 val2))))
        
        (equal-test-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (bool-val
	      (= val1 val2))))
        
        
        (greater-test-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (bool-val
	      (> val1 val2))))
        
                
        (less-test-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (bool-val
	      (< val1 val2))))
        
        (zero?-exp (exp1)
	  (let ((val1 (expval->num (value-of exp1 env))))
	    (if (zero? val1)
	      (bool-val #t)
	      (bool-val #f))))
        
        ;;cond operator
        (cond-exp (lst1 lst2)
                  (if (null? lst1) 
                      (num-val 0) 
                      ( if (expval->bool (value-of (car lst1) env))
                           (value-of (car lst2) env)
                           (value-of (cond-exp  (cdr lst1)  (cdr lst2)) env)
                           )
                      )
                  )
        
        ;;all-of operator
        (all-of-exp (exp lst) 
                    (if (null? lst)
                        (bool-val (expval->bool (value-of exp env)))
                        ( if (not (expval->bool (value-of exp env)))
                             (bool-val #f)
                             (value-of (all-of-exp  (car lst)  (cdr lst)) env)
                             )
                        )
                    )
        
        (pair-exp (exp1 exp2)
                  (pair-gen exp1 exp2)
                  )
        
        (unpair-exp (id ex1 ex2 in-expression)
                    (cases pr (value-of id env)
                      (pair-gen (val1 val2)
                                (value-of (let-exp ex1 val1
                                                   (let-exp ex2 val2 in-expression)
                                                   )
                                          env)
                                )
                      )
                    )
        
        (if-exp (exp0 exp1 exp2) 
          (if (expval->bool (value-of exp0 env))
            (value-of exp1 env)
            (value-of exp2 env)))
 
        (not-exp (exp) 
          (if (expval->bool (value-of exp env))
            (bool-val #f)
	    (bool-val #t)))
        
        (let-exp (id rhs body)       
          (let ((val (value-of rhs env)))
            (value-of body
              (extend-env id val env))))

        (proc-exp (bvar body)
	  (proc-val
	    (procedure bvar body env)))

        (app-exp (rator rand)          
          (let ((proc (expval->proc (value-of rator env)))
                (arg  (value-of rand env)))
	    (apply-procedure proc arg)))

        (letrec-exp (proc-name bvar proc-body letrec-body)
          (value-of letrec-body
            (extend-env-recursively proc-name bvar proc-body env)))

	    )))

  ;; apply-procedure : procedure * expval -> expval

  (define apply-procedure
    (lambda (proc1 arg)
      (cases proc proc1
        (procedure (bvar body saved-env)
          (value-of body (extend-env bvar arg saved-env))))))
  
  )
  


  
